#include "client.h"

Connection server;

#ifdef USE_DEFAULT_IP
char defaultIP[] = DEFAULT_IP;
#endif

int init(char* username, char* ip, int port)
{
	struct sockaddr_in serv_addr;
	int len;
	CODE cmd;
	server.connected = 1;
	server.inRoom  = -1;

	if ((server.sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0)
	{
		server.connected = 0;
		return 0;
	}

	memset(&serv_addr, 0, sizeof(serv_addr));
	serv_addr.sin_family = AF_INET;
	serv_addr.sin_port = htons(port);
	serv_addr.sin_addr.s_addr = inet_addr(ip);

	if (connect(server.sockfd, (struct sockaddr *) &serv_addr, sizeof(serv_addr)) < 0)
	{
		fputs("Connection error\n", stderr);
		server.connected = 0;
		return 0;
	}

	len = strlen(username) + 1;
	cmd = USERNAME;
	write(server.sockfd, &cmd, sizeof(CODE));
	write(server.sockfd, &len, sizeof(int));
	write(server.sockfd, username, len);
	fsync(server.sockfd);

	return server.connected;
}

void startReading(void)
{
	pthread_create(&server.thread, NULL, parseInput, NULL);
}

void *parseInput(void* param)
{
	int len;
	CODE cmd, cmd2;
	int sockfd = server.sockfd;

	while(server.connected)
	{
		read(sockfd, &cmd, sizeof(CODE));

		if(cmd & COMMAND)
		{
            switch(cmd)
            {
			case SERVERFULL:
				printf("Server is full.\n");
				server.connected = 0;
				close(sockfd);
				exit(0);
				break;
			case ACCEPTED:
				//break;
			case ADMIN:
				read(sockfd, &cmd, sizeof(CODE));
				break;
			case DENIED:
				break;
			case USERNAME:
				read(sockfd, &cmd, sizeof(int));
				server.connected = cmd;
				break;
			case KICK:
				read(sockfd, &cmd, sizeof(int));
				printf("Server Kicked you out of room %d.\n", cmd);
				server.inRoom = -1;
				break;
			case JOIN:
				read(sockfd, &cmd, sizeof(int));
				read(sockfd, &cmd2, sizeof(int));
				printf("Your room has a new member %s.\n", getPlayer(cmd2)->name);
				break;
			case LEAVE:
				read(sockfd, &cmd, sizeof(int));
				read(sockfd, &cmd, sizeof(int));
				printf("%s has left the room.\n",  getPlayer(cmd)->name);
				break;
			case ROOM:
				cmd = readRoom();

				displayRoomMembers(cmd);
				hastMoves(cmd);
				break;

			case PLAYER:
				readPlayer();
				break;
			case WINNER:
				{
				int winningPlayer, winningRoom, xoro;

				read(sockfd, &winningRoom, sizeof(winningRoom));
				read(sockfd, &winningPlayer, sizeof(winningPlayer));
				read(sockfd, &xoro, sizeof(xoro));

				printf("Winning Room: %d\n", winningRoom);
				if(winningPlayer == CATSGAME)
					printf("Cat's Game\n");
				else if(winningPlayer == server.connected)
					printf("You WIN!\n");
				else
					printf("You LOSE.\n");
				}
				break;
			default:
				printf("Server sent unknown command %d.\n", cmd);
				break;
			}
		}
		else
		{
			int roomNum = 0;
			read(sockfd, &roomNum, sizeof(roomNum));
			/* printf("Received command %d for room %d\n", cmd, roomNum); */

			storeMove(cmd);
			displayBoard();
			/* Movement Command recieved */
			Room* in = server.rm[server.inRoom];

			in->nextPlayer = NEXT(in, in->nextPlayer);
		}
	}
	return param;
}

void join(int i)
{
	int cmd = JOIN;
	int sockfd = server.sockfd;
	write(sockfd, &cmd, sizeof(CODE));
	cmd = i;
	write(sockfd, &cmd, sizeof(int));
	fsync(sockfd);
}

void leave(int i)
{
	int cmd = LEAVE;
	int sockfd = server.sockfd;
	write(sockfd, &cmd, sizeof(CODE));
	cmd = i;
	write(sockfd, &cmd, sizeof(int));
	fsync(sockfd);
}

Player* getPlayer(int id) {
	int i;
	Player** players = server.p;
	for(i = 0; i < MAXMEMBERS; i++)
		if(players[i] != NULL && players[i]->ID == id)
			return players[i];
	return NULL;
}

void displayRoomMembers(int rmid) {
	int j;
	Room* in = server.rm[rmid];
	printf("\n%s Members:\n", in->name);
	for(j = 0; j < in->maxForRoom; j++)
		if(in->players[j] != 0)
			printf("\tMember %d: %s\n", j+1, getPlayer(in->players[j])->name);
	puts("----------------------");
}

void haltConnection(void) 
{
	int sockfd = server.sockfd;
	int i;
	if(server.connected)
		close(sockfd);

	for(i = 0; i < MAXMEMBERS; i++) 
	{
		if(server.rm[i] != NULL)
			free(server.rm[i]);
	}
	for(i = 0; i < MAXMEMBERS; i++) 
	{
		if(server.rm[i] != NULL)
			free(server.p[i]);
	}
}

void sendMove(CODE cmd, int room)
{
	int sockfd = server.sockfd;
	printf("Sending command: %d for room %d\n", cmd, room);
	write(sockfd, &cmd, sizeof(cmd));
	write(sockfd, &room, sizeof(room));
}

int readRoom(void)
{
	int id, len;
	int sockfd = server.sockfd;

	read(sockfd, &id, sizeof(int));
	if(server.rm[id] == NULL) {
		server.rm[id] = calloc(1, sizeof(Room));
		server.rm[id]->players = calloc(MAXMEMBERS, sizeof(int));
	} else {
		free(server.rm[id]->name);
		memset(server.rm[id]->players, 0, MAXMEMBERS*sizeof(int));
	}
	server.rm[id]->ID = id;

	memset(server.rm[id]->moves, 0, sizeof(server.rm[id]->moves));

	read(sockfd, &len, sizeof(int));
	server.rm[id]->name = malloc(len);
	read(sockfd, server.rm[id]->name, len);
	read(sockfd, &server.rm[id]->gamesPlayed, sizeof(int));
	read(sockfd, &server.rm[id]->maxForRoom, sizeof(int));
	read(sockfd, server.rm[id]->players, server.rm[id]->maxForRoom*sizeof(int));
	read(sockfd, &server.rm[id]->numberInRoom, sizeof(int));
	read(sockfd, &server.rm[id]->playerX, sizeof(int));
	read(sockfd, &server.rm[id]->playerO, sizeof(int));
	read(sockfd, &server.rm[id]->admin, sizeof(int));
	read(sockfd, server.rm[id]->moves, sizeof(server.rm[id]->moves));

	return id;
}

int readPlayer(void) 
{
	int id, len, i;
	Player* ply;
	int sockfd = server.sockfd;

	read(sockfd, &id, sizeof(int));
	if((ply = getPlayer(id)) == NULL) {
		for(i = 0; i < MAXMEMBERS; i++)
			if(server.p[i] == NULL) {
				server.p[i] = calloc(1, sizeof(Player));
				ply = server.p[i];
				break;
			}
	}
	else
		free(ply->name);
	ply->ID = id;

	read(sockfd, &len, sizeof(int));
	ply->name = malloc(len);
	read(sockfd, ply->name, len);
	read(sockfd, &ply->gamesPlayed, sizeof(int));
	read(sockfd, &ply->wins, sizeof(int));
	read(sockfd, &ply->losses, sizeof(int));

	return id;
}

void hastMoves(int r) {
	int* serverMoves = server.rm[r]->moves;
	int i;
	for(i = 0; i < MOVES_IN_A_GAME; i++) {
		if(serverMoves[i] == 0) continue;
		storeMove(serverMoves[i]);
	}
}

int main(int argc, char* argv[])
{
	char username[BUFSIZ];
	int cmd, cmd2;
	char* ip;

	if (argc < 2)
	{
#ifndef USE_DEFAULT_IP
		fprintf(stderr, "Correct usage: %s Address\n", argv[0]);
		exit(1);
#else
		ip = defaultIP;
#endif
	} else
		ip = argv[1];

	printf("User name: ");
	fgets(username, sizeof(username), stdin);
	username[strlen(username)-1] = '\0';

	if(!init(username, ip, PORT)) {
		fprintf(stderr, "Connection could not be established.\n");
		exit(2);
	}

	startReading();

	while(server.connected) {
		printf("Please enter your command: ");
		scanf("%d", &cmd);
		getchar(); /* Chomp line feed */

		if (cmd == CL_CMDS)
		{
			printf("Commands:\n"
				"See this menu:    %d\n"
				"Change user name: %d\n"
				"Join a room:      %d\n"
				"Leave the room:   %d\n",
				CL_CMDS, CL_USERNAME, CL_JOIN, CL_LEAVE);
			if (checkIfInRoom())
			{
				puts("Available moves are numbered on board:");
				displayBoard();
			}
		}
		else if (cmd == CL_USERNAME)
		{
			printf("New user name: ");
			fgets(username, sizeof(username), stdin);
			username[strlen(username) - 1] = '\0'; /* Chomp line feed */
			cmd2 = USERNAME;
			write(server.sockfd, &cmd2, sizeof(CODE));
			cmd2 = strlen(username) + 1;
			write(server.sockfd, &cmd2, sizeof(CODE));
			write(server.sockfd, username, cmd2);	
		}
		else if (checkIfInRoom())
		{
			switch(cmd)
			{
			case CL_UL:
				cmd2 = UPPERLEFT;
				break;
			case CL_U:
				cmd2 = UPPER;
				break;
			case CL_UR:
				cmd2 = UPPERRIGHT;
				break;
			case CL_L:
				cmd2 = LEFT;
				break;
			case CL_C:
				cmd2 = CENTER;
				break;
			case CL_R:
				cmd2 = RIGHT;
				break;
			case CL_DL:
				cmd2 = LOWERLEFT;
				break;
			case CL_D:
				cmd2 = LOWER;
				break;
			case CL_DR:
				cmd2 = LOWERRIGHT;
				break;

			case CL_JOIN:
				fprintf(stderr, "You must not be in another room.\n");
				fprintf(stderr, "Please leave room %d first.\n",
					server.inRoom);
				break;

			case CL_LEAVE:
				cmd2 = LEAVE;
				write(server.sockfd, &cmd2, sizeof(cmd));
				write(server.sockfd, &server.inRoom,
					sizeof(server.inRoom));
				server.inRoom = -1;
				break;

			default:
				fprintf(stderr, "Recieved unkown code %d.\n", cmd);
			}
			if (isDirection(cmd))
			{
				write(server.sockfd, &cmd2, sizeof(CODE));
				write(server.sockfd, &server.inRoom,
					sizeof(server.inRoom));
			}
		}/* end if in a room */
		else /* NOT in a room */
		{
			switch(cmd)
			{
			case CL_UL: case CL_U: case CL_UR:
			case CL_L : case CL_C: case CL_R :
			case CL_DL: case CL_D: case CL_DR:
				fprintf(stderr, "Choose a room first!\n");
				break;

			case CL_LEAVE:
				fprintf(stderr, "Can't leave a room you're not in!\n");
				break;

			case CL_JOIN:
				cmd = JOIN;
				printf("Which room do you want to enter? ");
				scanf("%d", &cmd2);
				getchar(); /* Chomp linefeed */
				write(server.sockfd, &cmd, sizeof(cmd));
				write(server.sockfd, &cmd2, sizeof(cmd2));
				server.inRoom = cmd2;
				break;
			
			default:
				fprintf(stderr, "Recieved unkown code %d.\n", cmd);
			}
		}

		fsync(server.sockfd);
	}

	close(server.sockfd);

	return 0;
}

int checkIfInRoom()
{
	return server.inRoom >= 0;
}

int isInRange(int x, int lower, int upper)
{
	return x >= lower && x <= upper;
}

int isDirection(CODE c)
{
	return isInRange(c, CL_SQ_MIN, CL_SQ_MAX);
}

int getPlayerRepr(int sq)
{
	int move;
	if (!checkIfInRoom())
	{
		fprintf(stderr, "Not in a room\n");
		return -2;
	}
	else if (!isDirection(sq))
	{
		fprintf(stderr, "Invalid square number: %d\n", sq);
		return -1;
	}
	move = server.rm[server.inRoom]->moves[sq-1];
	if (move & X) return 'X';
	else if (move & O) return 'O';
	else return '0' + sq;
}

void displayBoard(void)
{
	Room* in = server.rm[server.inRoom];

	printf
	(
		"\n"
		CHAR_ROW
		BORDER_ROW
		CHAR_ROW
		BORDER_ROW
		CHAR_ROW,

		GPR(CL_UL), GPR(CL_U), GPR(CL_UR),
		GPR(CL_L ), GPR(CL_C), GPR(CL_R ),
		GPR(CL_DL), GPR(CL_D), GPR(CL_DR)
	);

	if (in->nextPlayer == server.connected)
		puts("-- Your turn --");
	else
		printf("-- %c's Turn --\n", in->nextPlayer == in->playerX ? 'X' : 'O');
}

void storeMove(int mv)
{
	int sq;

	switch (mv & ~(COMMAND | X | O))
	{
	case UPPERLEFT:
		sq = CL_UL;
		break;
	case UPPER:
		sq = CL_U;
		break;
	case UPPERRIGHT:
		sq = CL_UR;
		break;
	case LEFT:
		sq = CL_L;
		break;
	case CENTER:
		sq = CL_C;
		break;
	case RIGHT:
		sq = CL_R;
		break;
	case LOWERLEFT:
		sq = CL_DL;
		break;
	case LOWER:
		sq = CL_D;
		break;
	case LOWERRIGHT:
		sq = CL_DR;
		break;
	default:
		fprintf(stderr, "Unknown move: %d\n", mv);
		return;
	}

	server.rm[server.inRoom]->moves[sq-1] = mv;
}
